﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text.RegularExpressions;
using CmdTailCmd;

namespace regx
{
    public enum RegxSyntax      {DotNet, ECMA}
    public enum OutputDetails   {None, Smart, LineNumber, FileAndLine}
    public enum OutputSelection {Matched, Unmatched, All}

    public class RegxParams
    {  
    #region Regex pattern
        //=====================================================================
        [CmdTailSetting(DefaultArgument = true)]
        public string   Pattern         = null;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"rp", "rep", "repl", "replace"})]
        public string   ReplacePattern  = null;
    #endregion

    #region RegexOptions
        //=====================================================================
        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"i", "nocase"})]
        public bool CaseInsensitive = false;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"ipw"})]
        public bool IgnorePatternWhitespace = false;
        
        [CmdTailSetting(NameAllowSubstring = false)]
        public bool CultureInvariant = false;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"ExplicitCaptures"})]
        public bool ExplicitCapturesOnly = false;
        
        [CmdTailSetting(NameAllowSubstring = false, ValueAllowSubstring = false)]
        public RegxSyntax Syntax   = RegxSyntax.DotNet;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"rtl"})]
        public bool RightToLeft = false;
        
        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"u"})]
        public bool Uncompiled = false;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"ml"})]
        public bool Multiline = false;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"sl"})]
        public bool Singleline = false;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"all"})]
        public bool AllInput   = false;

        public RegexOptions MakeRegexOptions()
        { //-------------------------------------------------------------------
            return RegexOptions.None
                | (Uncompiled                   ? 0                                    : RegexOptions.Compiled)
                | (CaseInsensitive              ? RegexOptions.IgnoreCase              : 0)
                | (IgnorePatternWhitespace      ? RegexOptions.IgnorePatternWhitespace : 0)
                | (ExplicitCapturesOnly         ? RegexOptions.ExplicitCapture         : 0)
                | (CultureInvariant             ? RegexOptions.CultureInvariant        : 0)
                | (RightToLeft                  ? RegexOptions.RightToLeft             : 0)
                | (Singleline                   ? RegexOptions.Singleline              : 0)
                | (Multiline                    ? RegexOptions.Multiline               : 0)
                | ((Syntax == RegxSyntax.ECMA)  ? RegexOptions.ECMAScript              : 0);
        }
    #endregion

    #region Output format
        //=====================================================================
        public OutputDetails    Details           = OutputDetails.Smart;
        public bool             ShowRelativeNames = true;
        public OutputSelection  Output            = OutputSelection.Matched;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"v", "Invert"})]
        public bool InvertResults = false;

        [CmdTailSetting(NameAllowSubstring = false)]
        public string Format = null;
    #endregion

    #region Error output
        //=====================================================================
        [CmdTailSetting(NameAllowSubstring = false)] public bool NoWarnings     = false;
        [CmdTailSetting(NameAllowSubstring = false)] public bool ErrorsToStdOut = false;

        protected void DisplayError(string s)
        { //-------------------------------------------------------------------
            TextWriter writer = ErrorsToStdOut ? Console.Out : Console.Error;
            writer.WriteLine(s);
        }
    #endregion

    #region Input
        //=====================================================================
        public string   File    = null;

        [CmdTailSetting(NameAllowSubstring = false, AlternateNames = new string[]{"r"})]
        public bool     Recurse = false;

        [CmdTailSetting(NameAllowSubstring = false)]
        public string   Url     = null;

        [CmdTailSetting(NameAllowSubstring = false)]
        public bool     Fusk    = false;
    #endregion

    #region Debug and informational
        //=====================================================================
        [CmdTailSetting(NameAllowSubstring = false)]
        public bool     Debug           = false;
    #endregion


    #region Implementation
        //=====================================================================
        // Implementation
        protected StreamRexer     _searcher  = null;

        protected enum Sources {Single, Multiple}
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification="we want the general exception so we can report it (and not show a stack trace)")] 
        protected bool InitializeExecution(Sources srccount)
        {   //-------------------------------------------------------------------
            // We only need to examine InvertResults if Output is Matched. Any other setting overrides /Invert
			if (InvertResults && Output == OutputSelection.Matched) Output = OutputSelection.Unmatched;
            
            // Turn "Smart" into something with some usefulness
            if (Details == OutputDetails.Smart) Details = srccount == Sources.Single ? OutputDetails.LineNumber : OutputDetails.FileAndLine;

            // If they want Singleline or Multiline, we need to enable AllInput
            if (Singleline || Multiline) AllInput = true;

            // Make the searcher
            try
            {
                _searcher  = new StreamRexer(Pattern, MakeRegexOptions(), ReplacePattern, AllInput ? BreakIntoLines.No : BreakIntoLines.Yes);
            }
            catch (ArgumentOutOfRangeException e)
            {
                // Yes, "out of range" means "bad combination" in this case
                DisplayError("The combination of parameters is invalid:");
                DisplayError("    " + MakeRegexOptions().ToString());
                DisplayError(e.Message);
                return false;
            }
            catch (ArgumentException e)
            {
                // This is should mean the pattern was invalid as a regex
                DisplayError("Error " + e.Message);
                return false;
            }
            catch (Exception e)
            {
                DisplayError("An unexpected error occurred: " + e.Message);
                return false;
            }

            return true;
        }

        protected RegexResultFormatter MakeFormatter() {return MakeFormatter(null);}    // Because Code Analysis hates default parameters
        protected RegexResultFormatter MakeFormatter(string source)
        {   //-------------------------------------------------------------------
            RegexResultFormatter rrf = (null != Format) ? new RegexResultFormatter(regex:      (Regex)_searcher, 
                                                                                   Format:     Format,
                                                                                   SourceName: source)
                                                        : new RegexResultFormatter(Details:    Details, 
                                                                                   Selection:  Output, 
                                                                                   Replacing:  ReplacePattern != null, // string.Empty is a valid replacement pattern
                                                                                   SourceName: source);
            return rrf;
        }
    #endregion
    }
}